package com.xncoding.ReentrantLock;

import java.util.concurrent.locks.StampedLock;

public class Point {
    private double x, y;// 成员变量
    private final StampedLock sl = new StampedLock();// 锁实例

    /**
     * 写锁writeLock
     * 添加增量，改变当前point坐标的位置。
     * 先获取到了写锁，然后对point坐标进行修改，然后释放锁。
     * 写锁writeLock是排它锁，
     * 保证了其他线程调用move函数时候会被阻塞，
     * 直到当前线程显示释放了该锁，
     * 也就是保证了对变量x,y操作的原子性。
     */
    void move(double deltaX, double deltaY) {
        long stamp = sl.writeLock();
        try {
            x += deltaX;
            y += deltaY;
        } finally {
            sl.unlockWrite(stamp);
        }
    }

    /**
     * 乐观读锁tryOptimisticRead
     * 计算当前位置到原点的距离
     */
    double distanceFromOrigin() {
        long stamp = sl.tryOptimisticRead();    // 尝试获取乐观读锁(1)
        double currentX = x, currentY = y;      // 将全部变量拷贝到方法体栈内(2)

        // 检查票据是否可用，即写锁有没有被占用(3)
        if (!sl.validate(stamp)) {
            // 如果写锁被抢占，即数据进行了写操作，则重新获取
            stamp = sl.readLock();// 获取悲观读锁(4)
            try {
                // 将全部变量拷贝到方法体栈内(5)
                currentX = x;
                currentY = y;
            } finally {
                sl.unlockRead(stamp);// 释放悲观读锁(6)
            }
        }

        return Math.sqrt(currentX * currentX + currentY * currentY);// 真正读取操作，返回计算结果(7)
    }

    /**
     * 悲观读锁readLock
     * 如果当前坐标为原点则移动到指定的位置
     */
    void moveIfAtOrigin(double newX, double newY) {
        long stamp = sl.readLock();// 获取悲观读锁(1)
        try {
            // 如果当前点在原点则移动(2)
            while (x == 0.0 && y == 0.0) {
                long ws = sl.tryConvertToWriteLock(stamp);// 尝试将获取的读锁升级为写锁(3)

                if (ws != 0L) {
                    // 升级成功，则更新票据，并设置坐标值，然后退出循环(4)
                    stamp = ws;
                    x = newX;
                    y = newY;
                    break;
                } else {
                    // 读锁升级写锁失败，则释放读锁，显示获取独占写锁，然后循环重试(5)
                    sl.unlockRead(stamp);
                    stamp = sl.writeLock();
                }
            }
        } finally {
            sl.unlock(stamp);// 释放写锁(6)
        }
    }
}